package fr.umlv.cmis.gedconfig;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.LinkedHashMap;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.TransformerFactoryConfigurationError;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import kiyut.swing.util.EncodingUtils;

import org.apache.chemistry.opencmis.client.api.CmisObject;
import org.apache.chemistry.opencmis.client.api.Session;
import org.apache.chemistry.opencmis.commons.SessionParameter;
import org.apache.chemistry.opencmis.commons.exceptions.CmisObjectNotFoundException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

import fr.umlv.authentication.SessionHandler;
import fr.umlv.cmis.gedconfig.Shortcut.Type;
import fr.umlv.favorites.BookmarkView;
import fr.umlv.history.HistoryView;
import fr.umlv.model.ClientModel;

// TODO: Auto-generated Javadoc
/**
 * The Class XMLAccess.
 */
public class XMLAccess {
	
	/** The access. */
	private static XMLAccess access = null;
	
	/** The map bookmark. */
	private final LinkedHashMap<String, Shortcut> mapBookmark;
	
	/** The map history. */
	private final LinkedHashMap<String, Shortcut> mapHistory;
	
	/** The xml bookmark. */
	private Document xmlBookmark;
	
	/** The xml history. */
	private Document xmlHistory;
//	private final static String CMIS_USER_HOME = "/User Homes/";
	/** The Constant CMIS_USER_HOME. */
private final static String CMIS_USER_HOME = "/Espaces Utilisateurs/";
	
	/** The config file bookmark. */
	private final CmisObject configFileBookmark;
	
	/** The config file history. */
	private final CmisObject configFileHistory;

	/**
	 * Gets the single instance of XMLAccess.
	 *
	 * @return single instance of XMLAccess
	 */
	public static XMLAccess getInstance() {
		if (access == null)
			access = new XMLAccess();

		return access;
	}

	/**
	 * Instantiates a new xML access.
	 */
	private XMLAccess() {
		CmisObject tmpConfigFileHistory = null;
		CmisObject tmpConfigFileBookmark = null;

		mapBookmark = new LinkedHashMap<String, Shortcut>();
		mapHistory = new LinkedHashMap<String, Shortcut>();

		Session session = SessionHandler.getClientSession().getSession();
		String userName = SessionHandler.getClientSession().getSessionParameters().get(SessionParameter.USER);

		try {
			tmpConfigFileBookmark = session.getObjectByPath(CMIS_USER_HOME + userName + "/.gedconfig/");
		} catch (CmisObjectNotFoundException e) {
			tmpConfigFileBookmark = ClientModel.createFolder(CMIS_USER_HOME + userName + "/", ".gedconfig");
		}

		try {
			tmpConfigFileBookmark = session.getObjectByPath(CMIS_USER_HOME + userName + "/.gedconfig/bookmark.xml");
		} catch (CmisObjectNotFoundException e) {
			tmpConfigFileBookmark =
					ClientModel.createStreamCmisObject(CMIS_USER_HOME + userName + "/.gedconfig/", "bookmark.xml", "text/xml",
							ByteBuffer.wrap("".getBytes()));
		}

		try {
			tmpConfigFileHistory = session.getObjectByPath(CMIS_USER_HOME + userName + "/.gedconfig/history.xml");
		} catch (CmisObjectNotFoundException e) {
			tmpConfigFileHistory =
					ClientModel.createStreamCmisObject(CMIS_USER_HOME + userName + "/.gedconfig/", "history.xml", "text/xml",
							ByteBuffer.wrap("".getBytes()));
		}

		configFileBookmark = tmpConfigFileBookmark;
		configFileHistory = tmpConfigFileHistory;

		xmlBookmark = requestXmlBookmark();
		xmlHistory = requestXmlHistory();
	}

	/**
	 * Gets the bookmarks.
	 *
	 * @return the bookmarks
	 */
	public HashMap<String, Shortcut> getBookmarks() {
		return getShortcuts("bookmark");
	}

	/**
	 * Gets the history.
	 *
	 * @return the history
	 */
	public HashMap<String, Shortcut> getHistory() {
		return getShortcuts("history");
	}

	/**
	 * Gets the shortcuts.
	 *
	 * @param category the category
	 * @return the shortcuts
	 */
	public HashMap<String, Shortcut> getShortcuts(String category) {

		HashMap<String, Shortcut> mapSelected;
		Element racine;
		if (category.equals("bookmark")) {
			mapSelected = mapBookmark;
			racine = requestXmlBookmark().getDocumentElement();
		} else if (category.equals("history")) {
			mapSelected = mapHistory;
			racine = requestXmlHistory().getDocumentElement();
		} else {
			throw new IllegalArgumentException("Category must be bookmark or history");
		}
		
		mapSelected.clear();
		try {
			NodeList nodeList = racine.getElementsByTagName("shortcut");

			for (int i = 0; i < nodeList.getLength(); i++) {
				Element e = (Element) nodeList.item(i);
				String name = e.getAttribute("name");
				Shortcut.Type type = Shortcut.Type.valueOf(e.getAttribute("type"));
				String path = e.getAttribute("path");
				String id = e.getAttribute("id");
				mapSelected.put(name, new Shortcut(name, type, path, id));
			}
		} catch (NullPointerException e) {
			// Aucun élément n'est présent, donc il ne retourne une map
			// vide.
		}

		return mapSelected;
	}

	/**
	 * Request xml bookmark.
	 *
	 * @return the document
	 */
	private Document requestXmlBookmark() {
		return requestXmlShortcut("bookmark");
	}

	/**
	 * Request xml history.
	 *
	 * @return the document
	 */
	private Document requestXmlHistory() {
		return requestXmlShortcut("history");
	}
	
	/**
	 * Request xml shortcut.
	 *
	 * @param category the category
	 * @return the document
	 */
	private Document requestXmlShortcut(String category) {
		ByteBuffer bb;
		Document document = null;
		try {
			CmisObject configFileSelected = null;
			if (category.equals("bookmark")) {
				configFileSelected = configFileBookmark;
			} else if (category.equals("history")) {
				configFileSelected = configFileHistory;
			} else {
				throw new IllegalArgumentException("Category must be bookmark or history");
			}

			bb = ClientModel.getStreamCmisObject(configFileSelected);
			InputStream stream = new ByteArrayInputStream(bb.array());

			DocumentBuilderFactory fabrique = DocumentBuilderFactory.newInstance();
			DocumentBuilder constructeur = fabrique.newDocumentBuilder();
			constructeur.setErrorHandler(new GestionnaireDErreurs());

			Element racine;
			// Si le fichier est vide, on choisit de le préremplir
			if (stream.available() == 0) {
				document = constructeur.newDocument();
				document.setXmlVersion("1.0");
				document.setXmlStandalone(true);
				racine = document.createElement("shortcuts");
				document.appendChild(racine);
			} else {
				
				document = constructeur.parse(stream);
				racine = document.getDocumentElement();
			}

		} catch (IOException e) {
			e.printStackTrace();
		} catch (SAXException e) {
			e.printStackTrace();
		} catch (ParserConfigurationException e) {
			e.printStackTrace();
		}

		return document;
	}

	/**
	 * Adds the bookmark.
	 *
	 * @param name the name
	 * @param type the type
	 * @param path the path
	 * @param id the id
	 * @throws XMLNameAlreadyExistException the xML name already exist exception
	 */
	public void addBookmark(String name, Shortcut.Type type, String path, String id) throws XMLNameAlreadyExistException {
		addShortcut("bookmark", EncodingUtils.encodingToUTF8(name), type,EncodingUtils.encodingToUTF8(path), id);
		BookmarkView.getInstance().insert(name);
	}

	/**
	 * Adds the history.
	 *
	 * @param name the name
	 * @param type the type
	 * @param path the path
	 * @param id the id
	 */
	public void addHistory(String name, Shortcut.Type type, String path, String id) {
		
		Element racine = xmlHistory.getDocumentElement();

		NodeList nl = racine.getChildNodes();
		for (int i = 0; i < nl.getLength(); i++) {
			if (nl.item(i).getAttributes().getNamedItem("name").getNodeValue().equals(name)) {
				// Si il est déjà visité, on supprime
				nl.item(i).getParentNode().removeChild(nl.item(i));
			}
		}
		
		int maxSize = 10;
		if (nl.getLength() >= maxSize) {
			nl.item(maxSize-1).getParentNode().removeChild(nl.item(maxSize-1));
		}

		Element shortcut = xmlHistory.createElement("shortcut");
		shortcut.setAttribute("name", EncodingUtils.encodingToUTF8(name));
		shortcut.setAttribute("type", type.name());
		shortcut.setAttribute("path", EncodingUtils.encodingToUTF8(path));
		shortcut.setAttribute("id", id);
		racine.insertBefore(shortcut, racine.getFirstChild());
		ByteBuffer bb = ByteBuffer.wrap(XMLToString(xmlHistory).getBytes());

		ClientModel.updateStreamCmisObject(configFileHistory, bb);
		
		HistoryView.getInstance().update();
	}	
	
	/**
	 * Adds the shortcut.
	 *
	 * @param category the category
	 * @param name the name
	 * @param type the type
	 * @param path the path
	 * @param id the id
	 * @throws XMLNameAlreadyExistException the xML name already exist exception
	 */
	private void addShortcut(String category, String name, Type type, String path, String id) throws XMLNameAlreadyExistException {

		Document xmlSelected = null;

		if (category.equals("bookmark")) {
			xmlSelected = xmlBookmark;
		} else if (category.equals("history")) {
			xmlSelected = xmlHistory;
		} else {
			throw new IllegalArgumentException("Category must be bookmark or history");
		}

		Element racine = xmlSelected.getDocumentElement();

		NodeList nl = racine.getChildNodes();
		for (int i = 0; i < nl.getLength(); i++) {
			if (nl.item(i).getAttributes().getNamedItem("name").getNodeValue().equals(name)) {
				throw new XMLNameAlreadyExistException();
			}
		}

		Element shortcut = xmlSelected.createElement("shortcut");
		shortcut.setAttribute("name", name);
		shortcut.setAttribute("type", type.name());
		shortcut.setAttribute("path", path);
		shortcut.setAttribute("id", id);
		racine.appendChild(shortcut);
		ByteBuffer bb = ByteBuffer.wrap(XMLToString(xmlSelected).getBytes());

		ClientModel.updateStreamCmisObject(configFileBookmark, bb);
	}

	/**
	 * Del bookmark.
	 *
	 * @param name the name
	 * @throws XMLNameNotFoundException the xML name not found exception
	 */
	public void delBookmark(String name) throws XMLNameNotFoundException {
		delShortcut("bookmark", name);
	}
	
	public void delHistory(String name) throws XMLNameNotFoundException {
		delShortcut("histoty", name);
	}

//	public void delHistory(String name) throws XMLNameNotFoundException {
//		delShortcut("history", name);
//	}

	/**
 * Del shortcut.
 *
 * @param category the category
 * @param name the name
 * @throws XMLNameNotFoundException the xML name not found exception
 */
private void delShortcut(String category, String name) throws XMLNameNotFoundException {
		Document xmlSelected = null;

		if (category.equals("bookmark")) {
			xmlSelected = xmlBookmark;
		} else if (category.equals("history")) {
			xmlSelected = xmlHistory;
		} else {
			throw new IllegalArgumentException("Category must be bookmark or history");
		}

		Element racine = xmlSelected.getDocumentElement();

		NodeList nl = racine.getChildNodes();
		boolean flagIsPresent = false;
		for (int i = 0; i < nl.getLength(); i++) {
			if (nl.item(i).getAttributes().getNamedItem("name").getNodeValue().equals(name)) {
				flagIsPresent = true;
				nl.item(i).getParentNode().removeChild(nl.item(i));
			}
		}

		if (flagIsPresent == false) {
			throw new XMLNameNotFoundException();
		}

		ByteBuffer bb = ByteBuffer.wrap(XMLToString(xmlSelected).getBytes());

		ClientModel.updateStreamCmisObject(configFileBookmark, bb);
	}

	/**
	 * XML to string.
	 *
	 * @param doc the doc
	 * @return the string
	 */
	public static String XMLToString(Document doc) {

		StringWriter stw = new StringWriter();
		Transformer serializer;
		try {
			serializer = TransformerFactory.newInstance().newTransformer();
			serializer.transform(new DOMSource(doc), new StreamResult(stw));
		} catch (TransformerConfigurationException e) {
			e.printStackTrace();
		} catch (TransformerFactoryConfigurationError e) {
			e.printStackTrace();
		} catch (TransformerException e) {
			e.printStackTrace();
		}

		return stw.toString();

	}

	/**
	 * The Class GestionnaireDErreurs.
	 */
	public class GestionnaireDErreurs implements ErrorHandler {
		
		/**
		 * Message.
		 *
		 * @param e the e
		 * @return the string
		 */
		protected String message(SAXParseException e) {
			String message = "Message : " + e.getMessage() + "\n";
			message += "Ligne " + e.getLineNumber() + ", colonne " + e.getColumnNumber() + "\n";
			message += "Public id : " + e.getPublicId() + "\n";
			message += "System id : " + e.getSystemId();
			return message;
		}

		/**
		 * Prints the sax exception.
		 *
		 * @param e the e
		 */
		protected void printSAXException(SAXParseException e) {
			if (e.getException() != null) {
				e.getException().printStackTrace();
			}
		}

		/* (non-Javadoc)
		 * @see org.xml.sax.ErrorHandler#warning(org.xml.sax.SAXParseException)
		 */
		public void warning(SAXParseException exception) throws SAXException {
			printSAXException(exception);
		}

		/* (non-Javadoc)
		 * @see org.xml.sax.ErrorHandler#error(org.xml.sax.SAXParseException)
		 */
		public void error(SAXParseException exception) throws SAXException {
			printSAXException(exception);
		}

		/* (non-Javadoc)
		 * @see org.xml.sax.ErrorHandler#fatalError(org.xml.sax.SAXParseException)
		 */
		public void fatalError(SAXParseException exception) throws SAXException {
			String message = "*** Erreur fatale ***\n";
			message += message(exception);
			SAXException se = new SAXException(message, exception);
			throw se;
		}
	}

}
